/**
 **********************************************************************************************************************
 * @file    mykey.c
 * @brief   该文件提供按键动作识别功能
 *
 * @details  功能详细说明：
 *           + 按键初始化
 *           + 按键动作监听
 *           + 按键动作信息获取
 *           + 按键轮询任务
 *
 **********************************************************************************************************************
 *
 * 使用方式:
 *    1、使用前初始化函数 Key_Init
 *    2、周期调用函数 Key_Scan, 用来处理按键动作识别和事件触发
 *    3、使用其他函数对获取按键动作附加信息
 *
 **********************************************************************************************************************
 */

/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "key.h"

/* Private typedef ---------------------------------------------------------------------------------------------------*/
/* Private define ----------------------------------------------------------------------------------------------------*/

/* 按键工作模式定义 */
#define KEY_MODE_NORMAL 0     /* 普通模式 - 支持按下/松开触发 */
#define KEY_MODE_LONG_PRESS 1 /* 长按模式 - 支持长按检测和周期触发 */
#define KEY_MODE_CLICK 2      /* 连击模式 - 支持连续点击次数检测 */
#define KEY_MODE_CLICK_COMP 3 /* 连击复合模式 - 支持连击和长按的复合检测 */

/* 按键状态机状态定义 */
#define KEY_STATE_IDLE 0        /* 空闲状态 - 等待按键按下 */
#define KEY_STATE_PRESS 1       /* 按下状态 - 按键被按下但未达到长按时间 */
#define KEY_STATE_LOSSEN 2      /* 松开状态 - 按键松开但在连击间隔内 */
#define KEY_STATE_LONG_PRESS 3  /* 长按状态 - 按键按下超过长按时间阈值 */
#define KEY_STATE_LONG_LOSSEN 4 /* 长按松开状态 - 长按后松开 */

/* Private macro -----------------------------------------------------------------------------------------------------*/

/* 时间累加宏,防止溢出 */
#define TIME_MAX_VAL (uint32_t)0xffff /* 时间计数最大值 */
#define TIME_ADD(tic, time) (TIME_MAX_VAL - (tic) < (time) ? ((tic) = 0xffff) : (tic += time))

/* Private variables -------------------------------------------------------------------------------------------------*/

static KeyCfg_t *sg_pKeyTable = NULL; /* 按键配置表指针 */
static size_t sg_keyNum = 0;          /* 按键数量 */

/* Private function prototypes ---------------------------------------------------------------------------------------*/
/* Private function --------------------------------------------------------------------------------------------------*/

/**
 * @brief      按键初始化
 *
 * @param[in]  pCfgTable   按键配置表指针，包含所有按键的配置信息
 * @param[in]  num         按键数量，表示配置表中包含的按键个数
 *
 * @note       按键默认为普通模式，按下和松开都会触发回调函数
 *
 * @retval     0           初始化成功
 * @retval     -1          初始化失败
 */
int Key_Init(KeyCfg_t pCfgTable[], size_t num)
{
    uint8_t key;

    /* 保存按键配置表和按键数量 */
    sg_pKeyTable = pCfgTable;
    sg_keyNum = num;

    /* 遍历初始化所有按键 */
    for (key = 0; key < num; key++)
    {
        /* 初始化按键计数器和状态 */
        sg_pKeyTable[key].proc.clickCnt = 0;           /* 清零连击计数 */
        sg_pKeyTable[key].proc.uiPressTic = 0;         /* 清零按下计时器 */
        sg_pKeyTable[key].proc.uiLossenTic = 0;        /* 清零松开计时器 */
        sg_pKeyTable[key].proc.eKeyStateBak = KEY_OFF; /* 设置初始状态为松开 */
        sg_pKeyTable[key].proc.state = KEY_STATE_IDLE; /* 设置初始状态为空闲 */

        /* 清除回调函数并恢复默认配置 */
        Key_Listen((Key_t)key, NULL);
        Key_ResetDefaultConfig((Key_t)key);
    }

    return 0;
}

/**
 * @brief      监听指定按键状态，触发回调函数
 *
 * @param[in]  eKey         指定按键的标识符
 * @param[in]  pfnKeyFun    监听触发回调函数，当按键状态变化时会被调用
 *
 * @retval     0            设置成功
 * @retval     -1           设置失败，可能是按键未初始化或按键ID无效
 *
 * @note       回调函数会在按键状态变化时被调用，具体触发时机由按键模式决定
 */
int Key_Listen(Key_t eKey, KeyListen_f pfnKeyFun)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    /* 更新新配置中的回调函数 */
    sg_pKeyTable[eKey].proc.newConfig.pfnEventCallback = pfnKeyFun;

    /* 如果按键处于空闲状态,立即更新当前配置 */
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    /* 直接更新当前配置的回调函数 */
    sg_pKeyTable[eKey].proc.curConfig.pfnEventCallback = pfnKeyFun;
#endif
    return 0;
}

/**
 * @brief      配置按键恢复为默认模式
 *
 * @param[in]  eKey         指定按键的标识符
 *
 * @retval     0            配置成功
 * @retval     -1           配置失败，可能是按键未初始化或按键ID无效
 *
 * @note       默认普通模式，按下和松开都会触发回调函数
 */
int Key_ResetDefaultConfig(Key_t eKey)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    /* 设置新配置为默认值 */
    sg_pKeyTable[eKey].proc.newConfig.triggerWay = (KEY_TRIGGER_WAY_PRESS | KEY_TRIGGER_WAY_LOSSEN); /* 双边触发 */
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_NORMAL;                                        /* 普通模式 */
    sg_pKeyTable[eKey].proc.newConfig.longTime = 0;                                                  /* 无长按检测 */
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = 0;                                           /* 无重复触发 */
#else
    /* 直接设置当前配置为默认值 */
    sg_pKeyTable[eKey].proc.curConfig.triggerWay = (KEY_TRIGGER_WAY_PRESS | KEY_TRIGGER_WAY_LOSSEN);
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_NORMAL;
    sg_pKeyTable[eKey].proc.curConfig.longTime = 0;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = 0;
#endif
    return 0;
}

/**
 * @brief      配置按键为普通模式
 *
 * @param[in]  eKey            指定按键的标识符
 * @param[in]  triggerWay      触发方式，取值可以是 KEY_TRIGGER_WAY_PRESS 和 KEY_TRIGGER_WAY_LOSSEN 的任意组合
 * @param[in]  triggerInterval 按下不松开时重复触发事件的间隔时长，为0则只触发一次，单位毫秒
 *
 * @retval     0               配置成功
 * @retval     -1              配置失败，可能是按键未初始化或按键ID无效
 *
 * @note       该模式是只有按下/松开状态
 */
int Key_ConfigNormalMode(Key_t eKey, uint8_t triggerWay, uint16_t triggerInterval)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    /* 配置新的按键参数 */
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_NORMAL;            /* 设置为普通模式 */
    sg_pKeyTable[eKey].proc.newConfig.triggerWay = triggerWay;           /* 设置触发方式 */
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval; /* 设置触发间隔 */

    /* 如果按键当前空闲,则立即更新配置 */
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    /* 直接更新当前配置 */
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_NORMAL;
    sg_pKeyTable[eKey].proc.curConfig.triggerWay = triggerWay;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval;
#endif
    return 0;
}

/**
 * @brief      配置按键为长按模式
 *
 * @param[in]  eKey            指定按键的标识符
 * @param[in]  longPressTime   用来区分短按和长按的时间，单位毫秒
 * @param[in]  triggerInterval 当确认为长按时，按下不松开重复触发事件的间隔，为0则只触发一次，单位毫秒
 *
 * @retval     0               配置成功
 * @retval     -1              配置失败，可能是按键未初始化或按键ID无效
 *
 * @note       该模式是区分短按和长按两种状态，默认双边触发模式（按下触发/松开触发）
 */
int Key_ConfigLongPressMode(Key_t eKey, uint16_t longPressTime, uint16_t triggerInterval)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    /* 配置新的按键参数 */
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_LONG_PRESS;        /* 设置为长按模式 */
    sg_pKeyTable[eKey].proc.newConfig.longTime = longPressTime;          /* 设置长按时间阈值 */
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval; /* 设置触发间隔 */

    /* 如果按键当前空闲,则立即更新配置 */
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    /* 直接更新当前配置 */
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_LONG_PRESS;
    sg_pKeyTable[eKey].proc.curConfig.longTime = longPressTime;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval;
#endif
    return 0;
}

/**
 * @brief      配置按键为连击模式
 *
 * @param[in]  eKey            指定按键的标识符
 * @param[in]  triggerInterval 两次点击的间隔时间差，单位毫秒
 *
 * @retval     0               配置成功
 * @retval     -1              配置失败，可能是按键未初始化或按键ID无效
 *
 * @note       该模式是用于区分单击、双击和三击等一定时间内多次连击的场景使用
 */
int Key_ConfigClickMode(Key_t eKey, uint16_t triggerInterval)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    /* 配置新的按键参数 */
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_CLICK;             /* 设置为连击模式 */
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval; /* 设置连击间隔时间 */

    /* 如果按键当前空闲,则立即更新配置 */
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    /* 直接更新当前配置 */
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_CLICK;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval;
#endif
    return 0;
}

/**
 * @brief      配置按键为连击普通复合模式
 *
 * @param[in]  eKey            指定按键的标识符
 * @param[in]  triggerInterval 两次点击的间隔时间差，单位毫秒
 * @param[in]  pressMaxTime    按下超时时长，单位毫秒
 * @param[in]  triggerWay      触发方式，取值可以是 KEY_TRIGGER_WAY_PRESS 和 KEY_TRIGGER_WAY_LOSSEN 的任意组合
 *
 * @retval     0               配置成功
 * @retval     -1              配置失败，可能是按键未初始化或按键ID无效
 *
 * @note       该模式用于区分长时间按下、单击、双击和三击等一定时间内多次连击的场景使用
 *             按键空闲状态下当按下超过一定时长时，则不再触发连击模式，变为普通模式
 */
int Key_ConfigClickCompMode(Key_t eKey, uint16_t triggerInterval, uint16_t pressMaxTime, uint8_t triggerWay)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    /* 配置新的按键参数 */
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_CLICK_COMP;        /* 设置为连击复合模式 */
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval; /* 设置连击间隔时间 */
    sg_pKeyTable[eKey].proc.newConfig.triggerWay = triggerWay;           /* 设置触发方式 */
    sg_pKeyTable[eKey].proc.newConfig.longTime = pressMaxTime;           /* 设置长按时间阈值 */

    /* 如果按键当前空闲,则立即更新配置 */
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    /* 直接更新当前配置 */
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_CLICK_COMP;        /* 设置为连击复合模式 */
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval; /* 设置连击间隔时间 */
    sg_pKeyTable[eKey].proc.curConfig.triggerWay = triggerWay;           /* 设置触发方式 */
    sg_pKeyTable[eKey].proc.curConfig.longTime = pressMaxTime;           /* 设置长按时间阈值 */
#endif
    return 0;
}

/**
 * @brief      获取指定按键的点击次数
 *
 * @param[in]  eKey         指定按键的标识符，取值为 @ref Key_t
 *
 * @return     uint8_t      点击次数，如果按键模式不支持连击计数或按键无效，则返回0
 *
 * @attention  需要将按键配置为连击模式或连击普通复合模式
 */
uint8_t Key_GetClickCount(Key_t eKey)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return 0;
    }

    /* 检查按键模式是否支持连击计数 */
    if (sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_CLICK &&
        sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_CLICK_COMP)
    {
        return 0;
    }

    /* 返回当前连击次数 */
    return sg_pKeyTable[eKey].proc.clickCnt;
}

/**
 * @brief      判断按键点击次数是否符合要求
 *
 * @param[in]  eKey         指定按键的标识符
 * @param[in]  count        期望的点击次数
 *
 * @retval     true         点击次数符合要求
 * @retval     false        点击次数不符合要求，或按键未设为连击模式
 *
 * @attention  需要将按键配置为连击模式
 */
bool Key_IsClickCount(Key_t eKey, uint8_t count)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return false;
    }

    /* 检查是否为连击模式 */
    if (sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_CLICK)
    {
        return false;
    }

    /* 比较连击次数是否匹配 */
    if (sg_pKeyTable[eKey].proc.clickCnt != count)
    {
        return false;
    }

    return true;
}

/**
 * @brief      判断按键状态是否为触发了长按状态
 *
 * @param[in]  eKey         指定按键的标识符，取值为 @ref Key_t
 *
 * @retval     true         按键处于长按状态
 * @retval     false        按键处于短按状态或按键无效，或者按键未配置为长按模式
 *
 * @attention  需要将按键配置为长按模式
 */
bool Key_IsLongPress(Key_t eKey)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return false;
    }

    /* 检查是否为长按模式 */
    if (sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_LONG_PRESS)
    {
        return false;
    }

    /* 检查是否处于长按状态 */
    if (!(sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_PRESS ||
          sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_LOSSEN))
    {
        return false;
    }

    return true;
}

/**
 * @brief      判断按键状态是否符合要求
 *
 * @param[in]  eKey         指定按键的标识符
 * @param[in]  triggerWay   按键触发状态，取值可以是 KEY_TRIGGER_WAY_PRESS 和 KEY_TRIGGER_WAY_LOSSEN 的任意组合
 *
 * @retval     true         按键状态符合要求
 * @retval     false        按键状态不符合要求，或按键无效
 *
 * @attention  适用于普通模式和长按模式，长按模式时需要通过调用函数Key_IsLongPress判断是否处于长按
 */
bool Key_IsTriggerWayMet(Key_t eKey, uint8_t triggerWay)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return false;
    }

    /* 检查是否满足按下触发条件 */
    if ((triggerWay & KEY_TRIGGER_WAY_PRESS) &&
        (sg_pKeyTable[eKey].proc.state == KEY_STATE_PRESS ||
         sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_PRESS))
    {
        return true;
    }

    /* 检查是否满足松开触发条件 */
    if ((triggerWay & KEY_TRIGGER_WAY_LOSSEN) &&
        (sg_pKeyTable[eKey].proc.state == KEY_STATE_LOSSEN ||
         sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_LOSSEN))
    {
        return true;
    }

    return false;
}

/**
 * @brief      判断按键状态和按下时长是否符合要求
 *
 * @param[in]  eKey         指定按键的标识符
 * @param[in]  triggerWay   按键触发状态，取值可以是 KEY_TRIGGER_WAY_PRESS 和 KEY_TRIGGER_WAY_LOSSEN 的任意组合
 * @param[in]  time         需要比较的按下时长，单位毫秒
 *
 * @retval     true         按键状态和按下时长都符合要求
 * @retval     false        按键状态或按下时长不符合要求，或按键无效
 *
 * @note       此函数会先检查按键触发状态是否符合要求，再检查按下时长是否达到指定值
 */
bool Key_IsPressTimeMet(Key_t eKey, uint8_t triggerWay, uint16_t time)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return false;
    }

    /* 检查触发方式是否满足 */
    if (!Key_IsTriggerWayMet(eKey, triggerWay))
    {
        return false;
    }

    /* 检查按下时间是否达到要求 */
    if (sg_pKeyTable[eKey].proc.uiPressTic < time)
    {
        return false;
    }

    return true;
}

/**
 * @brief      判断按键状态和松开时长是否符合要求
 *
 * @param[in]  eKey         指定按键的标识符
 * @param[in]  triggerWay   按键触发状态，取值可以是 KEY_TRIGGER_WAY_PRESS 和 KEY_TRIGGER_WAY_LOSSEN 的任意组合
 * @param[in]  time         需要比较的松开时长，单位毫秒
 *
 * @retval     true         按键状态和松开时长都符合要求
 * @retval     false        按键状态或松开时长不符合要求，或按键无效
 *
 * @note       此函数会先检查按键触发状态是否符合要求，再检查松开时长是否达到指定值
 */
bool Key_IsLossenTimeMet(Key_t eKey, uint8_t triggerWay, uint16_t time)
{
    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return false;
    }

    /* 检查触发方式是否满足 */
    if (!Key_IsTriggerWayMet(eKey, triggerWay))
    {
        return false;
    }

    /* 检查松开时间是否达到要求 */
    if (sg_pKeyTable[eKey].proc.uiLossenTic < time)
    {
        return false;
    }

    return true;
}

/**
 * @brief      处理普通按键的状态变化
 *
 * @param[in]  pKey          指向按键处理结构体的指针，用于跟踪按键状态和配置
 * @param[in]  newState      当前按键的新状态，是按下还是松开
 * @param[in]  intervalTime  自上次状态变化以来的时间间隔，用于处理重复触发
 *
 * @retval     true          需要根据当前状态变化调用回调函数
 * @retval     false         不需要调用回调函数
 *
 * @note       此函数根据按键的当前状态和配置，决定是否需要调用回调函数，并更新按键的状态。
 *             它处理四种主要的按键状态变化：空闲、按下、松开以及重复触发。
 */
static bool ProcessNormalKeyState(KeyProc_t *pKey, Key_t newState, uint16_t intervalTime)
{
    bool isCallFun = false; /* 标记是否需要触发回调函数 */

    /* 根据当前按键状态执行相应的处理 */
    switch (pKey->state)
    {
    /* 空闲状态处理 */
    case KEY_STATE_IDLE:
        if (newState == KEY_ON) /* 检测到按键按下 */
        {
            pKey->state = KEY_STATE_PRESS; /* 切换到按下状态 */
            pKey->uiPressTic = 0;          /* 清零按下计时器 */

            /* 如果配置了按下触发,则标记需要回调 */
            if (pKey->curConfig.triggerWay & KEY_TRIGGER_WAY_PRESS)
            {
                isCallFun = true;
            }
        }
        break;
    /* 按下状态处理 */
    case KEY_STATE_PRESS:
        if (newState == KEY_OFF) /* 检测到按键松开 */
        {
            pKey->state = KEY_STATE_LOSSEN; /* 切换到松开状态 */

            /* 如果配置了松开触发,则标记需要回调 */
            if (pKey->curConfig.triggerWay & KEY_TRIGGER_WAY_LOSSEN)
            {
                isCallFun = true;
            }
        }
        else /* 按键持续按下 */
        {
            /* 如果配置了重复触发间隔 */
            if (0 != pKey->curConfig.triggerInterval)
            {
                TIME_ADD(pKey->uiPressTic, intervalTime); /* 累加按下时间 */

                /* 达到重复触发间隔,触发回调并重新计时 */
                if (pKey->uiPressTic > pKey->curConfig.triggerInterval)
                {
                    pKey->uiPressTic = 0;
                    isCallFun = true;
                }
            }
        }
        break;
    /* 松开状态处理 */
    case KEY_STATE_LOSSEN:
        pKey->state = KEY_STATE_IDLE; /* 直接切换回空闲状态 */
        break;
    }

    return isCallFun;
}

/**
 * @brief      处理长按按键状态
 *
 * @param[in]  pKey          指向按键处理结构体的指针，包含按键状态和配置信息
 * @param[in]  newState      新的按键状态，表示按键当前是按下还是松开
 * @param[in]  intervalTime  自上次按键状态变化以来的时间间隔，用于计算长按时间
 *
 * @retval     true          需要触发按键回调函数
 * @retval     false         不需要触发按键回调函数
 *
 * @note       该函数根据当前按键状态和新的按键事件，处理按键的长按逻辑，并确定是否需要触发回调函数
 */
static bool ProcessLongPressKeyState(KeyProc_t *pKey, Key_t newState, uint16_t intervalTime)
{
    bool isCallFun = false; /* 标记是否需要触发回调函数 */

    switch (pKey->state)
    {
    /* 空闲状态处理 */
    case KEY_STATE_IDLE:
        if (newState == KEY_ON) /* 检测到按键按下 */
        {
            pKey->state = KEY_STATE_PRESS; /* 切换到按下状态 */
            pKey->uiPressTic = 0;          /* 清零按下计时器 */

            /* 如果配置了按下触发,则标记需要回调 */
            if (pKey->curConfig.triggerWay & KEY_TRIGGER_WAY_PRESS)
            {
                isCallFun = true;
            }
        }
        break;
    /* 按下状态处理 */
    case KEY_STATE_PRESS:
        if (newState == KEY_OFF) /* 检测到按键松开(短按) */
        {
            pKey->state = KEY_STATE_LOSSEN; /* 切换到松开状态 */
            isCallFun = true;               /* 触发短按回调 */
        }
        else /* 按键持续按下 */
        {
            TIME_ADD(pKey->uiPressTic, intervalTime); /* 累加按下时间 */

            /* 达到长按时间阈值 */
            if (pKey->uiPressTic >= pKey->curConfig.longTime)
            {
                pKey->state = KEY_STATE_LONG_PRESS; /* 切换到长按状态 */
                pKey->uiPressTic = 0;               /* 清零计时器 */

                /* 如果配置了按下触发,则标记需要回调 */
                if (pKey->curConfig.triggerWay & KEY_TRIGGER_WAY_PRESS)
                {
                    isCallFun = true;
                }
            }
        }
        break;
    /* 短按松开状态处理 */
    case KEY_STATE_LOSSEN:
        pKey->state = KEY_STATE_IDLE; /* 直接切换回空闲状态 */
        break;
    /* 长按状态处理 */
    case KEY_STATE_LONG_PRESS:
        if (newState == KEY_OFF) /* 检测到按键松开 */
        {
            pKey->state = KEY_STATE_LONG_LOSSEN; /* 切换到长按松开状态 */

            /* 如果配置了松开触发,则标记需要回调 */
            if (pKey->curConfig.triggerWay & KEY_TRIGGER_WAY_LOSSEN)
            {
                isCallFun = true;
            }
        }
        else /* 按键持续长按 */
        {
            TIME_ADD(pKey->uiPressTic, intervalTime); /* 累加长按时间 */

            /* 如果配置了重复触发间隔 */
            if (0 != pKey->curConfig.triggerInterval)
            {
                /* 达到重复触发间隔,触发回调并重新计时 */
                if (pKey->uiPressTic > pKey->curConfig.triggerInterval)
                {
                    pKey->uiPressTic = 0;
                    isCallFun = true;
                }
            }
        }
        break;
    case KEY_STATE_LONG_LOSSEN:       /* 长按松开状态处理 */
        pKey->state = KEY_STATE_IDLE; /* 直接切换回空闲状态 */
        break;
    }

    return isCallFun;
}

/**
 * @brief      处理连击按键状态
 *
 * @param[in]  pKey          指向按键处理结构体的指针，包含按键状态和配置信息
 * @param[in]  newState      新的按键状态，表示按键当前是按下还是松开
 * @param[in]  intervalTime  自上次按键状态变化以来的时间间隔，用于计算连击时间
 *
 * @retval     true          需要触发按键回调函数
 * @retval     false         不需要触发按键回调函数
 *
 * @note       该函数根据当前按键状态和新的按键事件，处理按键的连击逻辑，并确定是否需要触发回调函数
 */
static bool ProcessClickKeyState(KeyProc_t *pKey, Key_t newState, uint16_t intervalTime)
{
    bool isCallFun = false; /* 标记是否需要触发回调函数 */

    switch (pKey->state)
    {
    /* 空闲状态处理 */
    case KEY_STATE_IDLE:
        if (newState == KEY_ON) /* 检测到按键按下 */
        {
            pKey->state = KEY_STATE_PRESS; /* 切换到按下状态 */
            pKey->uiPressTic = 0;          /* 清零按下计时器 */
            pKey->uiLossenTic = 0;         /* 清零松开计时器 */
            pKey->clickCnt = 0;            /* 清零连击计数 */
        }
        break;
    /* 按下状态处理 */
    case KEY_STATE_PRESS:
        if (newState == KEY_OFF) /* 检测到按键松开 */
        {
            pKey->state = KEY_STATE_LOSSEN; /* 切换到松开状态 */
            pKey->uiLossenTic = 0;          /* 清零松开计时器 */
            pKey->clickCnt++;               /* 增加连击计数 */
        }
        break;
    /* 松开状态处理 */
    case KEY_STATE_LOSSEN:
        TIME_ADD(pKey->uiLossenTic, intervalTime); /* 累加松开时间 */

        /* 松开时间超过连击间隔 */
        if (pKey->uiLossenTic > pKey->curConfig.triggerInterval)
        {
            isCallFun = true;             /* 触发连击回调 */
            pKey->state = KEY_STATE_IDLE; /* 切换回空闲状态 */
        }
        else /* 在连击间隔内 */
        {
            if (newState == KEY_ON) /* 检测到新的按下 */
            {
                pKey->state = KEY_STATE_PRESS; /* 切换回按下状态继续检测连击 */
            }
        }
        break;
    }

    return isCallFun;
}

/**
 * @brief      处理点击组合键状态
 *
 * @param[in]  pKey          指向按键处理结构体的指针，用于记录按键处理状态
 * @param[in]  newState      当前按键状态，是按下还是松开
 * @param[in]  intervalTime  自上次按键状态变化以来的时间间隔
 *
 * @retval     true          需要触发按键回调函数
 * @retval     false         不需要触发按键回调函数
 *
 * @note       此函数根据按键的当前状态和上次状态，决定是否需要触发按键回调函数，并更新按键处理状态。
 *             它通过状态机的方式处理按键的连击和长按逻辑。
 */
static bool ProcessClickCompKeyState(KeyProc_t *pKey, Key_t newState, uint16_t intervalTime)
{
    bool isCallFun = false; /* 标记是否需要触发回调函数 */

    switch (pKey->state)
    {
    /* 空闲状态处理 */
    case KEY_STATE_IDLE:
        if (newState == KEY_ON) /* 检测到按键按下 */
        {
            pKey->state = KEY_STATE_PRESS; /* 切换到按下状态 */
            pKey->uiPressTic = 0;          /* 清零按下计时器 */
            pKey->uiLossenTic = 0;         /* 清零松开计时器 */
            pKey->clickCnt = 0;            /* 清零连击计数 */
        }
        break;
    /* 按下状态处理 */
    case KEY_STATE_PRESS:
        if (newState == KEY_OFF) /* 检测到按键松开 */
        {
            pKey->state = KEY_STATE_LOSSEN; /* 切换到松开状态 */
            pKey->uiLossenTic = 0;          /* 清零松开计时器 */

            /* 按下时间小于长按阈值,视为连击 */
            if (pKey->uiPressTic < pKey->curConfig.longTime)
            {
                pKey->clickCnt++; /* 增加连击计数 */
            }
            else /* 按下时间超过长按阈值,视为长按 */
            {
                /* 如果配置了松开触发,则标记需要回调 */
                if (pKey->curConfig.triggerWay & KEY_TRIGGER_WAY_LOSSEN)
                {
                    isCallFun = true;
                }
            }
        }
        else /* 按键持续按下 */
        {
            /* 还未发生连击时才检测长按 */
            if (pKey->clickCnt == 0)
            {
                TIME_ADD(pKey->uiPressTic, intervalTime); /* 累加按下时间 */

                /* 达到长按阈值且配置了按下触发 */
                if (pKey->uiPressTic >= pKey->curConfig.longTime &&
                    (pKey->curConfig.triggerWay & KEY_TRIGGER_WAY_PRESS))
                {
                    isCallFun = true; /* 触发长按回调 */
                }
            }
        }
        break;
    /* 松开状态处理 */
    case KEY_STATE_LOSSEN:
        TIME_ADD(pKey->uiLossenTic, intervalTime); /* 累加松开时间 */

        /* 松开时间超过连击间隔 */
        if (pKey->uiLossenTic > pKey->curConfig.triggerInterval)
        {
            isCallFun = true;             /* 触发连击回调 */
            pKey->state = KEY_STATE_IDLE; /* 切换回空闲状态 */
        }
        else /* 在连击间隔内 */
        {
            if (newState == KEY_ON) /* 检测到新的按下 */
            {
                pKey->state = KEY_STATE_PRESS; /* 切换回按下状态继续检测连击 */
            }
        }
        break;
    }

    return isCallFun;
}

/**
 * @brief      按键扫描周期任务
 *
 * @param[in]  sysTime      系统运行时间，单位毫秒，用于计算按键按下和松开的时间
 *
 * @retval     0            扫描成功
 * @retval     -1           扫描失败，可能是按键未初始化
 *
 * @note       此函数应该周期性调用，用于处理按键状态变化检测和事件触发
 */
int Key_Scan(uint32_t sysTime)
{
    uint8_t key;
    Key_t eKeyNewState;
    bool isCallFun = false;
    static uint32_t s_sysTimeBak = 0; /* 上次扫描的系统时间 */

    /* 参数有效性检查 */
    if (sg_pKeyTable == NULL || sg_keyNum == 0)
    {
        return -1;
    }

    /* 首次调用初始化时间戳 */
    if (s_sysTimeBak == 0)
    {
        s_sysTimeBak = sysTime;
    }

    /* 获取所有按键的当前状态 */
    for (key = 0; key < sg_keyNum; key++)
    {
        sg_pKeyTable[key].proc.eKeyState = sg_pKeyTable[key].pfnKeyState();
    }

    /* 处理每个按键的状态 */
    for (key = 0; key < sg_keyNum; key++)
    {
        /* 检测按键状态变化 */
        if (sg_pKeyTable[key].proc.eKeyStateBak != sg_pKeyTable[key].proc.eKeyState)
        {
            eKeyNewState = sg_pKeyTable[key].proc.eKeyStateBak;
        }
        else
        {
            eKeyNewState = sg_pKeyTable[key].proc.eKeyState;
        }

        /* 根据按键模式调用对应的处理函数 */
        switch (sg_pKeyTable[key].proc.curConfig.mode)
        {
        case KEY_MODE_NORMAL:
            isCallFun = ProcessNormalKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
        case KEY_MODE_LONG_PRESS:
            isCallFun = ProcessLongPressKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
        case KEY_MODE_CLICK:
            isCallFun = ProcessClickKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
        case KEY_MODE_CLICK_COMP:
            isCallFun = ProcessClickCompKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
        default:

            break;
        }

        /* 如果需要触发回调且回调函数存在,则调用回调 */
        if (isCallFun && NULL != sg_pKeyTable[key].proc.curConfig.pfnEventCallback)
        {
            sg_pKeyTable[key].proc.curConfig.pfnEventCallback((Key_t)key);
        }

        /* 按键空闲时更新配置和清除计数 */
        if (sg_pKeyTable[key].proc.state == KEY_STATE_IDLE)
        {
#if defined(_CONFIG_DELAY_UPDATE_)
            sg_pKeyTable[key].proc.curConfig = sg_pKeyTable[key].proc.newConfig; /* 按键空闲时才更新配置 */
#endif
            sg_pKeyTable[key].proc.clickCnt = 0; /* 清零连击计数 */
        }

        /* 保存当前状态为上次状态 */
        sg_pKeyTable[key].proc.eKeyStateBak = sg_pKeyTable[key].proc.eKeyState;
    }

    /* 更新系统时间 */
    s_sysTimeBak = sysTime;
    return 0;
}
